package util

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
)

//NewContainerRuntimeAPIClient ...
func NewContainerRuntimeAPIClient() ContainerRuntimeClient {
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
	if err != nil {
		return nil
	}
	return &containerRuntimeAPIClient{cli: cli, ctx: context.Background()}
}

//containerRuntimeAPIClient
type containerRuntimeAPIClient struct {
	cli *client.Client
	ctx context.Context
}

func (c *containerRuntimeAPIClient) Version() (string, error) {
	sv, err := c.cli.ServerVersion(c.ctx)
	if err != nil {
		return "", err
	}
	cv := c.cli.ClientVersion()
	return fmt.Sprintf("client:%s \nServeer:%s \nPlatform:%s", cv, sv.Version, sv.Platform.Name), nil
}

func (c *containerRuntimeAPIClient) ContainerStart(containerOption *ContainerOption) string {
	exposedPorts := make(map[nat.Port]struct{})
	portBindings := make(map[nat.Port][]nat.PortBinding)
	//volumes := make(map[string]struct{})
	binds := make([]string, 0)
	for k, v := range containerOption.PublishPorts {
		exposedPorts[nat.Port(k+"/tcp")] = struct{}{}
		portBindings[nat.Port(k+"/tcp")] = []nat.PortBinding{
			{
				HostIP:   "",
				HostPort: v,
			},
		}
	}

	for k, v := range containerOption.Volume {
		//volumes[k] = struct{}{}
		binds = append(binds, k+":"+v)
	}

	config := &container.Config{
		Hostname:     containerOption.Hostname,
		Image:        containerOption.ImageName,
		ExposedPorts: nat.PortSet(exposedPorts),
		//Volumes:      volumes,
	}

	hostConfig := &container.HostConfig{
		PortBindings: nat.PortMap(portBindings),
		Binds:        binds,
	}

	resp, err := c.cli.ContainerCreate(c.ctx, config, hostConfig, nil, nil, containerOption.ContainerName)
	if err != nil {
		fmt.Println(err.Error())
		return ""
	}
	if err := c.cli.ContainerStart(c.ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		fmt.Println(err.Error())
		return ""
	}
	return resp.ID
}

func (c *containerRuntimeAPIClient) RegistryLogin(username string, password string, registry string) bool {
	authConfig := types.AuthConfig{
		Username:      username,
		Password:      password,
		ServerAddress: registry,
	}
	if _, err := c.cli.RegistryLogin(c.ctx, authConfig); err != nil {
		fmt.Println(err.Error())
		return false
	}
	return true
}
func (c *containerRuntimeAPIClient) ImagePull(imageName string, version string) string {
	if version == "" {
		version = "latest"
	}
	imageName = imageName + ":" + version

	out, err := c.cli.ImagePull(c.ctx, imageName, types.ImagePullOptions{})
	if err != nil {
		fmt.Println(err.Error())
		return ""
	}
	defer out.Close()
	buf := new(bytes.Buffer)
	buf.ReadFrom(out)
	newStr := buf.String()
	return newStr
}

func (c *containerRuntimeAPIClient) ImagePullByAuth(imageName string, version string, username string, password string) string {
	if version == "" {
		version = "latest"
	}
	imageName = imageName + ":" + version
	imagePullOptions := types.ImagePullOptions{}
	if username != "" {
		authConfig := types.AuthConfig{
			Username: username,
			Password: password,
		}
		encodedJSON, err := json.Marshal(authConfig)
		if err != nil {
			panic(err)
		}
		authStr := base64.URLEncoding.EncodeToString(encodedJSON)
		imagePullOptions = types.ImagePullOptions{RegistryAuth: authStr}
	}
	out, err := c.cli.ImagePull(c.ctx, imageName, imagePullOptions)
	if err != nil {
		fmt.Println(err.Error())
		return ""
	}
	defer out.Close()
	buf := new(bytes.Buffer)
	buf.ReadFrom(out)
	newStr := buf.String()
	status := strings.Split(newStr, "\n")
	i := len(status)
	imgID := strings.Replace(strings.Split(status[i-3], ":")[3], "\"}", "", -1)
	return imgID
}

func (c *containerRuntimeAPIClient) ListAllImage() []ImageInfo {
	images, err := c.cli.ImageList(c.ctx, types.ImageListOptions{})
	if err != nil {
		fmt.Println(err.Error())
	}
	var ii []ImageInfo
	for _, image := range images {
		fmt.Println(image)
		i := &ImageInfo{}
		r := strings.Split(image.RepoTags[0], ":")
		i.Repository = r[0]
		i.Tag = r[1]
		i.ImageID = strings.Split(image.ID, ":")[1]
		i.Size = formatByteSize(uint64(image.Size))
		i.Created = time.Unix(int64(image.Created), 0).Format("2006-01-02 15:04:05")
		ii = append(ii, *i)
	}
	return ii
}

func (c *containerRuntimeAPIClient) ContainerStopAndRemove(containerID string) {
	c.cli.ContainerStop(c.ctx, containerID, nil)
	c.cli.ContainerRemove(c.ctx, containerID, types.ContainerRemoveOptions{})

}

//ImageInfo ...
type ImageInfo struct {
	Repository string
	Tag        string
	ImageID    string
	Size       string
	Created    string
}
